package com.dlc.shop.allinpay.config;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.dlc.shop.allinpay.OpenClient;
import com.dlc.shop.common.allinpay.AbstractReq;
import com.dlc.shop.common.allinpay.bean.BizParameter;
import com.dlc.shop.common.allinpay.bean.OpenConfig;
import com.dlc.shop.common.allinpay.bean.OpenRequest;
import com.dlc.shop.common.allinpay.bean.OpenResponse;
import com.dlc.shop.common.allinpay.member.resp.FileUploadBeforeResp;
import com.dlc.shop.common.allinpay.member.resp.TokenResp;
import com.dlc.shop.common.allinpay.util.SecretUtils;
import com.dlc.shop.common.bean.Allinpay;
import com.dlc.shop.common.config.Constant;
import com.dlc.shop.common.exception.YamiShopBindException;
import com.dlc.shop.common.response.ResponseEnum;
import com.dlc.shop.common.util.DateUtils;
import com.dlc.shop.service.SysConfigService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.interfaces.RSAPrivateKey;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author TRACK
 */
@Component
@RequiredArgsConstructor
public class AllinpayConfig {

    private final SysConfigService sysConfigService;

    private static final Logger LOG = LoggerFactory.getLogger(AllinpayConfig.class);
    public static final String METHOD_PREFIX = "allinpay.yunst.";
    public static final String SIGN_TYPE = "SHA256WithRSA";
    public static final String FILE_UPLOAD_CODE = "10000";
    public static final Integer NUM = 1024;
    public static final String SUCCESS_MSG = "接口调用成功";
    public static final String SUCCESS_MSG_OK = "OK(接口调用成功)";

    protected OpenClient client;

    public void configClient() {
        Allinpay allinpay = sysConfigService.getSysConfigObject(Constant.ALLINPAY_CONFIG, Allinpay.class);
        OpenConfig oc = new OpenConfig(allinpay.getRequestUrl(), allinpay.getAppId(), allinpay.getAppSecret(), allinpay.getAppPrivateCertPath(), allinpay.getAppPrivateKey(), allinpay.getAppCertPath());
        try {
            client = new OpenClient(oc);
        } catch (Exception e) {
            LOG.error("异常：", e);
        }
        if (Objects.isNull(client)) {
            throw new YamiShopBindException("yami.sys.config.exception.allinpayError");
        }
    }


    public <T> T sendData(AbstractReq req, Class<T> clazz) {
        String responseData = getResponseData(req);
        if (clazz == null) {
            return null;
        }
        return JSON.parseObject(responseData, clazz);
    }

    public void sendCreateData(AbstractReq req) {
        getCreateResponseData(req);
    }

    public String sendUrlData(AbstractReq req) {
        return this.getUrlResponseData(req);
    }

    public <T> List<T> sendDataGetArray(AbstractReq req, Class<T> clazz) {
        String responseData = getResponseData(req);
        if (clazz == null) {
            return null;
        }
        return JSON.parseArray(responseData, clazz);
    }

    private String getResponseData(AbstractReq req) {
        OpenResponse response = getOpenResponse(req);
        LOG.info(JSON.toJSONString(response));
        if (ResponseEnum.OK.getMsg().toUpperCase(Locale.ROOT).equals(response.getSubCode())) {
            return response.getData();
        }
        throw new YamiShopBindException(response.getSubMsg());
    }

    private OpenResponse getOpenResponse(AbstractReq req) {
        configClient();
        String method = METHOD_PREFIX + req.getService() + StrUtil.C_DOT + req.getMethod();
        BizParameter bizParameter = JSON.parseObject(JSON.toJSONString(req), BizParameter.class);
        return client.execute(method, bizParameter);
    }

    private void getCreateResponseData(AbstractReq req) {
        OpenResponse response = getOpenResponse(req);
        LOG.info(JSON.toJSONString(response));
        // 创建会员发生异常返回码也是ok，只有msg不同，所以校验一下msg
        boolean msgIsSuccess = SUCCESS_MSG_OK.equals(response.getMsg()) || SUCCESS_MSG.equals(response.getMsg()) || ResponseEnum.OK.getMsg().toUpperCase(Locale.ROOT).equals(response.getMsg());
        if (ResponseEnum.OK.getMsg().toUpperCase(Locale.ROOT).equals(response.getSubCode()) && msgIsSuccess) {
            return;
        }
        throw new YamiShopBindException(response.getSubMsg());
    }

    /**
     * 获取通联url响应数据
     *
     * @param req
     * @return
     */
    private String getUrlResponseData(AbstractReq req) {
        OpenResponse response = getOpenResponse(req);
        return JSON.parseObject(response.getData()).getString("url");
    }

    public Boolean signVerify(Map<String, String> map) throws UnsupportedEncodingException {
        configClient();
        String sign = map.remove("sign");
        map.remove("signType");
        String signedValue = map.entrySet().stream()
                .map(e -> e.getKey() + "=" + e.getValue()).sorted()
                .collect(Collectors.joining("&"));
        return client.checkSign(URLDecoder.decode(signedValue, StandardCharsets.UTF_8), sign);
    }

    public OpenClient getClient() {
        configClient();
        return client;
    }

    public String uploadFile(String imgUrl) {
        try {
            Allinpay allinpay = sysConfigService.getSysConfigObject(Constant.ALLINPAY_CONFIG, Allinpay.class);
            File file = new File(imgUrl);
            URL url = new URL(imgUrl);
            URLConnection connection = url.openConnection();
            InputStream inputStream = connection.getInputStream();
            //创建HttpClient
            CloseableHttpClient httpClient = HttpClients.createDefault();
            HttpPost httpPost = new HttpPost(allinpay.getRequestUrl().substring(0, allinpay.getRequestUrl().length() - 12) + "/file/open/uploadFile");
            MultipartEntityBuilder builder = MultipartEntityBuilder.create();
            // 绑定文件参数，传入文件流和contentType，此处也可以继续添加其他formData参数
            String timeStamp = DateUtils.dateToString(new Date());
            builder.addBinaryBody("file", inputStream, ContentType.MULTIPART_FORM_DATA, file.getName());
            builder.addTextBody("appId", allinpay.getAppId());
            builder.addTextBody("timestamp", timeStamp);
            //计算MD5开始
            URLConnection connectionMd5 = url.openConnection();
            InputStream inputStreamMd5 = connectionMd5.getInputStream();
            String md5 = getFileMd5(inputStreamMd5);
            builder.addTextBody("md5", md5);
            //计算MD5结束
            //加签开始
            Map<String, Object> resMap = new HashMap<>(3);
            resMap.put("appId", allinpay.getAppId());
            resMap.put("timestamp", timeStamp);
            resMap.put("md5", md5);
            String source = getSignedValue(resMap);
            RSAPrivateKey privateKey = (RSAPrivateKey) SecretUtils.loadPrivateKey(null,
                    allinpay.getAppPrivateCertPath(), allinpay.getAppPrivateKey());
            String sign = SecretUtils.sign(privateKey, source, "SHA256WithRSA");
            builder.addTextBody("sign", sign);
            //加签结束
            HttpEntity entity = builder.build();
            httpPost.setEntity(entity);
            //执行提交
            HttpResponse response = httpClient.execute(httpPost);
            HttpEntity responseEntity = response.getEntity();
            if (responseEntity != null) {
                //将响应的内容转换成字符串
                String result = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8);
                //此处根据服务器返回的参数转换，这里返回的是JSON格式
                LOG.info("通联文件上传响应内容{}", result);
                FileUploadBeforeResp fileUploadResp = JSON.parseObject(result, FileUploadBeforeResp.class);
                if (Objects.equals(fileUploadResp.getCode(), FILE_UPLOAD_CODE)) {
                    return JSON.parseObject(fileUploadResp.getData(), TokenResp.class).getToken();
                } else {
                    throw new YamiShopBindException(fileUploadResp.getSubMsg());
                }
            }
        } catch (Exception ex) {
            LOG.error("异常：", ex);
        }
        return null;
    }

    private static String getSignedValue(Map<String, Object> params) {
        Map<String, Object> copy = new TreeMap<>();
        params.forEach((k, v) -> {
            if (v != null) {
                copy.put(k, v);
            }
        });
        copy.remove("sign");
        StringBuilder sb = new StringBuilder(Constant.INITIAL_CAPACITY);
        copy.forEach((k, v) -> sb.append(k).append("=").append(v).append("&"));
        return sb.isEmpty() ? "" : sb.substring(0, sb.length() - 1);
    }

    public static String getFileMd5(InputStream in) {
        if (in == null) {
            return null;
        }
        MessageDigest digest;
        byte[] buffer = new byte[1024];
        int len;
        try {
            digest = MessageDigest.getInstance("MD5");
            while ((len = in.read(buffer, 0, NUM)) != -1) {
                digest.update(buffer, 0, len);
            }
            in.close();
        } catch (Exception e) {
            LOG.error("异常：", e);
            return null;
        }
        byte[] bytes = Base64.encodeBase64(digest.digest());
        return new String(bytes, StandardCharsets.UTF_8);
    }

    public OpenRequest validateAllinpayAndReturn(String data, HttpServletRequest request) throws UnsupportedEncodingException {
        //乱码解决，这段代码在出现乱码时使用
        request.setCharacterEncoding("utf-8");
        Map<String, String> params = getParams(request);
        LOG.info("data:{}", data);

        // 验签
        Boolean flag = signVerify(params);
        if (!flag) {
            LOG.error("验签失败+data={}", params);
            throw new YamiShopBindException("yami.allinpay.exception.verificationError");
        }
        String paramsStr = JSON.toJSONString(params, SerializerFeature.MapSortField);
        return JSONObject.parseObject(paramsStr, OpenRequest.class);
    }

    public Map<String, String> getParams(HttpServletRequest request) {
        //获取POST过来反馈信息
        Map<String, String> params = new TreeMap<>();
        Map<String, String[]> requestParams = request.getParameterMap();
        for (String name : requestParams.keySet()) {
            String[] values = requestParams.get(name);
            String valueStr = "";
            for (int i = 0; i < values.length; i++) {
                valueStr = (i == values.length - 1) ? valueStr + values[i]
                        : valueStr + values[i] + ",";
            }
            params.put(name, valueStr);
        }
        return params;
    }

}
